package guzb.cnblogs.security.industrydemo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import guzb.cnblogs.security.industrydemo.auth.AuthorityService;
import guzb.cnblogs.security.industrydemo.auth.MyUserDetailsService;
import guzb.cnblogs.security.industrydemo.auth.SecurityMapper;
import guzb.cnblogs.security.industrydemo.auth.restful.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import java.util.ArrayList;
import java.util.List;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class WebSecuritySetting {

    @Autowired
    SecurityMapper userEntityMapper;

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    AuthorityService authorityService;

    /** 权限配置 */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        // 1. 传统BS模式的认证配置
        httpSecurity
                .authorizeHttpRequests((auth) -> {
                    // 增加 rest 页面，作为前后端分离模式下的演示主页面
                    auth.antMatchers("/books").authenticated()
                         .antMatchers("/system").hasRole("ADMIN")
                         .antMatchers("/", "/index", "/rest","/static/**").permitAll();
                })
                .formLogin(withDefaults()).httpBasic()
                .and()
                .logout().deleteCookies("remove")
                .invalidateHttpSession(true).logoutUrl("/logout");

        /*
         * 2. 前后端分离模式下的认证过滤器配置
         *    这里约定，uri 以 api 开头的，都属于前后端分离模式下的web资源，其中：
         *    /api/auth/** 是与认证相关的资源
         *    /api/** 下非 auth 的 uri, 是业务资源
         *
         * 2.1 前后端分离模式下的用户密码认证Provider和Filter
         **/
        AuthenticationManager parentAuthManager = httpSecurity.getSharedObject(AuthenticationManager.class);
        MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = createMyUsernamePasswordAuthenticationFilter(parentAuthManager);
        httpSecurity.addFilterAfter(myUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        // 2.2 前后端分离模式下的授权(访问控制）处理
        httpSecurity.authorizeHttpRequests(authorizeRegistry -> {
            authorizeRegistry
                    .antMatchers("/api/app/**")
                    .access(new MyUriAuthorizationManager(authorityService));
        });

        // 2.3 前后端分离模式下的访问拒绝逻辑处理, 必须添加在默认的 ExceptionTranslationFilter 过滤器之后
        MyUriAccessDeniedProcessingFilter restfulUriAccessDeniedFilter = new MyUriAccessDeniedProcessingFilter(objectMapper);
        httpSecurity.addFilterAfter(restfulUriAccessDeniedFilter, ExceptionTranslationFilter.class);

        // 2.4 放开前端的跨域限制，这一步仅仅是为了方便编写这个样例的前端页面。正常开发应该禁止跨域访问
        //     spring mvc的跨域配置是通过 覆盖 WebMvcConfigurer 的 addCorsMappings 方法或添加CorsFilter来实现的
        //     spring security 也为CorsFilter提供了添加入口，这里为了快速生效，使用了最简短的代码量来完成
        CorsConfigurationSource permitAllCorsConfig = request -> new CorsConfiguration().applyPermitDefaultValues();
        httpSecurity.cors().configurationSource(permitAllCorsConfig);

        return httpSecurity.build();
    }

    /**
     * 创建自定义的用户名密码过滤器
     * 注1：不要添加 @Bean 注解，即不要将 Security 的认证过滤器添加到 SpringContext 中，
     *    因为这个过滤器类是标准的 javax.servlet.Filter, 如果加入到 SpringContext 中，
     *    则会成为 Servlet 服务器的一标准过滤器，这样该过滤器就重复两次了
     *
     * 注2：添加自定义认证过滤器更正统的做法，是编写一个AbstractHttpConfigurer，在里边组装所有相关的自定义认证组件, 但这里没有这样做
     * */
    public MyUsernamePasswordAuthenticationFilter createMyUsernamePasswordAuthenticationFilter(AuthenticationManager parentAuthManager) throws Exception {
        MyUsernamePasswordAuthenticationFilter myAuthFiler = new MyUsernamePasswordAuthenticationFilter();

        AuthenticationManager myAuthManager = buildMyAuthenticationManger(parentAuthManager);
        myAuthFiler.setAuthenticationManager(myAuthManager);

        MyUserPasswordAuthenticationResultHandler myAuthResultHandler = new MyUserPasswordAuthenticationResultHandler(objectMapper);
        myAuthFiler.setAuthenticationSuccessHandler(myAuthResultHandler);
        myAuthFiler.setAuthenticationFailureHandler(myAuthResultHandler);

        return myAuthFiler;
    }

    /**
     * 手动构建 AuthenticationManager 对象。
     * 默认情况，框架会自动构建一个全局的 AuthenticationManager 对象，里边包含了 DaoAuthenticationProvider。
     *
     * 现在我们要将创建一个局部的 AuthenticationManager, 它仅作用于自定义的认证过滤器中，
     * 因此，需要将自定义的 MyUsernamePasswordAuthenticationProvider 加入到这个 AuthenticationManger 中
     */
    public AuthenticationManager buildMyAuthenticationManger(AuthenticationManager parent) throws Exception {
        List<AuthenticationProvider> authProviders = new ArrayList<>(1);
        MyUserDetailsService myUserDetailsService = customerDbUserDetailService();
        MyUsernamePasswordAuthenticationProvider myAuthProvider = new MyUsernamePasswordAuthenticationProvider(myUserDetailsService);
        authProviders.add(myAuthProvider);
        return new ProviderManager(authProviders, parent);
    }

    /**
     * 提供一个默认的密码加密器
     */
    @Bean
    public PasswordEncoder createPasswordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 基于Sqlite3数据库的自定义用户明细服务
     */
    @Bean
    public MyUserDetailsService customerDbUserDetailService() {
        return new MyUserDetailsService(userEntityMapper);
    }

}
